﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Commands;
using Microsoft.Practices.ObjectBuilder;
using Kent.Boogaart.HelperTrinity;
using WPFCABShell.Shell.Interface.Services.Command;
using WPFCABShell.Shell.Interface.Services.Menu;
using SCSFContrib.CompositeUI.WPF.Commands;
using CAB = Microsoft.Practices.CompositeUI.Commands;
using WPF = System.Windows.Input;

namespace WPFCABShell.$safeprojectname$.Menu
{
	/// <summary>
	/// An implementation of <see cref="IMenuService"/> for the WPF CAB shell.
	/// </summary>
	public sealed class MenuService : IMenuService
	{
		private readonly WorkItem _workItem;

		[InjectionConstructor]
		public MenuService([Dependency(NotPresentBehavior=NotPresentBehavior.Throw)] WorkItem workItem)
		{
			_workItem = workItem;
		}

		public IMenuItem Add(string targetSiteName, ICommand command, int? groupIndex, int? index, string siteName)
		{
			ArgumentHelper.AssertNotNull(targetSiteName, "targetSiteName");
			ArgumentHelper.AssertNotNull(command, "command");
			ExceptionHelper.ThrowIf(!_workItem.UIExtensionSites.Contains(targetSiteName), "TargetSiteNotFound", targetSiteName);

			//create the menu item
			CustomMenuItem menuItem = GetMenuItem(targetSiteName, command, groupIndex, index, siteName);

			//add the menu item to the target site
			_workItem.UIExtensionSites[targetSiteName].Add(menuItem);

			//register new site if given
			if (siteName != null)
			{
				_workItem.UIExtensionSites.RegisterSite(siteName, menuItem);
			}

			return menuItem;
		}

		public void Remove(IMenuItem menuItem)
		{
			ArgumentHelper.AssertNotNull(menuItem, "menuItem");

			if (menuItem.SiteName != null)
			{
				_workItem.UIExtensionSites.UnregisterSite(menuItem.SiteName);
			}

			//remove the menu item from the extension site
			_workItem.UIExtensionSites[menuItem.TargetSiteName].Remove(menuItem);
		}

		private CustomMenuItem GetMenuItem(string targetSiteName, ICommand command, int? groupIndex, int? index, string siteName)
		{
			Debug.Assert(targetSiteName != null);
			Debug.Assert(command != null);
			
			CustomMenuItem menuItem = new CustomMenuItem(targetSiteName, command, siteName);
			GroupingItemsControlUIAdapter.SetGroupIndex(menuItem, groupIndex);
			GroupingItemsControlUIAdapter.SetIndex(menuItem, index);

			return menuItem;
		}

		#region Supporting Types

		private sealed class CustomMenuItem : MenuItem, IMenuItem
		{
			private readonly string _targetSiteName;
			private readonly string _siteName;

			public string TargetSiteName
			{
				get
				{
					return _targetSiteName;
				}
			}

			public new ICommand Command
			{
				get
				{
					return base.Command as ICommand;
				}
			}

			public string SiteName
			{
				get
				{
					return _siteName;
				}
			}

			public new event EventHandler<EventArgs> SubmenuOpened;

			public new event EventHandler<EventArgs> SubmenuClosed;

			public CustomMenuItem(string targetSiteName, ICommand command, string siteName)
			{
				Debug.Assert(targetSiteName != null);
				Debug.Assert(command != null);
				_targetSiteName = targetSiteName;
				_siteName = siteName;
				base.Command = command;

				if (command.Name != null)
				{
					Header = command.Name;
				}

				Image image = new Image();
				Binding binding = new Binding("Image");
				binding.Source = command;
				BindingOperations.SetBinding(image, Image.SourceProperty, binding);
				Icon = image;

				//set up a dynamic reference to MenuItem style
				//this is a hack around the fact that styles are not inherited like they should be and
				//overriding DefaultResourceKey achieves squat, and SetResourceReference throws unless we're
				//loaded. Aaaagh!
				Loaded += delegate
				{
					SetResourceReference(FrameworkElement.StyleProperty, typeof(MenuItem));
				};
			}

			protected override void OnInitialized(EventArgs e)
			{
				base.OnInitialized(e);
				CreateBinding();
			}

			protected override void OnSubmenuOpened(RoutedEventArgs e)
			{
				base.OnSubmenuOpened(e);
				EventHelper.Raise(SubmenuOpened, this, EventArgs.Empty);
			}

			protected override void OnSubmenuClosed(RoutedEventArgs e)
			{
				base.OnSubmenuClosed(e);
				EventHelper.Raise(SubmenuClosed, this, EventArgs.Empty);
			}

			protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
			{
				base.OnItemsChanged(e);

				if (IsInitialized)
				{
					CreateBinding();
				}
			}

			private void CreateBinding()
			{
				//create multibinding to visibility of all children
				MultiBinding multiBinding = new MultiBinding();
				multiBinding.Converter = MenuItemVisibilityConverter.Instance;
				multiBinding.Mode = BindingMode.OneWay;

				foreach (object o in Items)
				{
					UIElement uiElement = o as UIElement;

					if (o != null)
					{
						Binding binding = new Binding("Visibility");
						binding.Source = uiElement;
						binding.Mode = BindingMode.OneWay;
						multiBinding.Bindings.Add(binding);
					}
				}

				if (Command.IsNullCommand)
				{
					//menu items without corresponding WPF commands should be hidden when they have no children
					Binding binding = new Binding("Items");
					binding.Source = this;
					binding.Mode = BindingMode.OneWay;
					multiBinding.Bindings.Add(binding);
				}

				BindingOperations.ClearBinding(this, VisibilityProperty);
				BindingOperations.SetBinding(this, MenuItem.VisibilityProperty, multiBinding);
			}
		}

		private sealed class MenuItemVisibilityConverter : IMultiValueConverter
		{
			public static readonly MenuItemVisibilityConverter Instance = new MenuItemVisibilityConverter();

			private MenuItemVisibilityConverter()
			{
			}

			public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
			{
				if (values.Length == 0)
				{
					return Visibility.Visible;
				}

				foreach (object value in values)
				{
					if (value is Visibility)
					{
						if ((Visibility)value == Visibility.Visible)
						{
							return Visibility.Visible;
						}
					}
					else
					{
						Debug.Assert(value is ItemCollection);
						ItemCollection items = value as ItemCollection;

						foreach (object item in items)
						{
							UIElement uiElement = item as UIElement;

							if ((uiElement != null) && (uiElement.Visibility == Visibility.Visible))
							{
								return Visibility.Visible;
							}
						}
					}
				}

				return Visibility.Collapsed;
			}

			public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
			{
				throw new NotImplementedException();
			}
		}

		#endregion
	}
}
